package com.martian.service.impl;

import com.github.pagehelper.PageHelper;
import com.martian.common.enums.BaseErrorCodeEnum;
import com.martian.common.exception.SysException;
import com.martian.common.util.Embedding;
import com.martian.common.util.SqlQuotationUtil;
import com.martian.common.util.ThreadLocalMap;
import com.martian.mapper.MovieMapper;
import com.martian.mapper.UserMapper;
import com.martian.pojo.dto.AllMovieDto;
import com.martian.pojo.dto.MovieDetail;
import com.martian.pojo.dto.MovieSimilarDto;
import com.martian.pojo.dto.RecMovieDto;
import com.martian.pojo.entity.Movie;
import com.martian.pojo.entity.MovieStatistic;
import com.martian.pojo.entity.UserStatistic;
import com.martian.pojo.form.LoadMovieForm;
import com.martian.pojo.form.MovieRatingForm;
import com.martian.service.MovieService;
import com.martian.service.ThreadService;
import com.martian.service.redis.RedisRepository;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static com.martian.service.impl.UserServiceImpl.getUserWatchHistory;

/**
 * @author martian
 * @version V1.0.0
 * @Description
 * @date 2023-03-20 9:05
 */
@Service
public class MovieServiceImpl implements MovieService{
    @Resource
    MovieMapper movieMapper;
    @Resource
    UserMapper userMapper;
    @Resource
    ThreadService threadService;
    @Resource
    private RedisRepository redisRepository;

    @Override
    public List<Movie> getAllMovie() {
        return movieMapper.getAllMovie();
    }

    @Override
    public AllMovieDto getAllMovieByPage(Integer page, Integer pageSize) {
        if (page<=0 || pageSize>100){
            throw new SysException(BaseErrorCodeEnum.OVER_RANGE_ERROR);
        }
        PageHelper.startPage(page,pageSize);
        List<Movie> partMovies = movieMapper.getAllMovie();
        Integer totalNum = movieMapper.getNumOfAllMovies();
        return new AllMovieDto(partMovies,totalNum);
    }

    /**
     * 前端搜索框的接口，每次只能选择搜索名称或类别
     * @param info
     * @param searchType
     * @return
     */
    @Override
    public AllMovieDto getMovieByNameOrGenre(Integer page, Integer pageSize, String info,Integer searchType){
        if (info.equals("")){
            PageHelper.startPage(page,pageSize);
            List<Movie> allMovies = movieMapper.getAllMovieForSearchList();
            Integer num = movieMapper.getNumOfAllMovies();
            return new AllMovieDto(allMovies,num);
        }
        if(searchType == 0){
            //按照电影名查询
            PageHelper.startPage(page,pageSize);
            info = SqlQuotationUtil.replaceQuotation(info);
            List<Movie> allMovies = movieMapper.getAllMovieByName(info);
            Integer num = movieMapper.getNumOfAllMoviesByName(info);
            return new AllMovieDto(allMovies,num);
        }else{
            //按照电影类别查询
            PageHelper.startPage(page,pageSize);
            info = SqlQuotationUtil.replaceQuotation(info);
            List<Movie> allMovies = movieMapper.getAllMovieByGenre(info);
            Integer num = movieMapper.getNumOfAllMoviesByGenre(info);
            return new AllMovieDto(allMovies,num);
        }
    }

    @Override
    public void insertMovie(LoadMovieForm loadMovieForm){
        // 如果前端不传movieId, 则默认为0
        if (loadMovieForm.getMovieId()!=0){
            movieMapper.editMovie(loadMovieForm);
        }else{
            movieMapper.insertMovie(loadMovieForm);
        }
    }

    @Override
    public void deleteMovie(Integer movieId){
        movieMapper.deleteMovieById(movieId);
    }

    @Override
    public void batchDeleteMovies(List<Integer> batchDeleteMovies){
        movieMapper.batchDeleteMovie(batchDeleteMovies);
    }
    @Override
    public List<RecMovieDto> getHotMovies(Integer size){
        return movieMapper.selectRecMovieById(movieMapper.getHotedMovieIds(size));
    }

    /**
     * 相似电影的召回层
     * @param movieId
     * @return
     */
    public HashMap<Integer,RecMovieDto> filterSimilarMovieIds(Integer movieId,Movie movie){
        //数据库的电影类型字段为 Adventure|Children|Fantasy，需要切分
        String[] genres = movie.getMovieGenres().split("\\|");
        HashMap<Integer,RecMovieDto> candidatesMovie = new HashMap<>();
        for (String genre : genres) {
            Map parmap = new HashMap();
            parmap.put("genre",genre);
            parmap.put("size",30);
            //第一路：选取各电影类型中平均分排名前30的电影
            List<RecMovieDto> movies = movieMapper.selectMovieIdsByGenreOrderByAvgRating(parmap);
            for (RecMovieDto recMovieDto : movies) {
                candidatesMovie.put(recMovieDto.getMovieId(),recMovieDto);
            }
        }
        //第二路：选取平均分排名前20的电影
        List<RecMovieDto> avgRatingMovies = movieMapper.selectMoviesByAvgRating(20);
        for (RecMovieDto avgRatingMovie : avgRatingMovies) {
            candidatesMovie.put(avgRatingMovie.getMovieId(),avgRatingMovie);
        }
        //第三路：选取上映时间与目标电影上映时间两年内的综合评分前10的电影
        List<RecMovieDto> releasedYearMovies = movieMapper.selectMoviesByReleasedYear(Integer.valueOf(movie.getMovieReleasedYear()),2);
        for (RecMovieDto recMovieDto : releasedYearMovies) {
            candidatesMovie.put(recMovieDto.getMovieId(),recMovieDto);
        }
        //去掉自身
        candidatesMovie.remove(movieId);
        return candidatesMovie;
    }

    /**
     * @TODO 有些电影的Embedding不存在，访问其对应的相似电影会出错。
     * @Description 相似电影：召回层-多路召回 排序层-Embedding相似度排序
     * @param movieId
     * @param size
     * @return
     */
    @Override
    public List<RecMovieDto> getSimilarMovie(Integer movieId, Integer size) {
        //获取目标电影Embedding @TODO 这里并不是所有电影都有Embedding
        //先查询redis缓存
        MovieSimilarDto similarMovies = redisRepository.selectSimilarMovieIdsById(movieId);
        if (similarMovies==null){
            String movieStrEmb = redisRepository.selectMovieDeepWalkEmb(movieId);
            Movie movie = movieMapper.selectMovieById(movieId);
            //@TODO 这里Emb为空，说明DeepWalk算法游走到该结点，可以采取一些其他策略生成，例如选择该电影的同类电影Embedding平均值
            if(movieStrEmb==null){//如果emb为空，则按照类型和年份返回相似电影
                List<Integer> candidatedIds = movieMapper.selectMovieIdsByGenresAndYear(movie.getMovieGenres(), Integer.valueOf(movie.getMovieReleasedYear()),2);
                candidatedIds.remove(movieId);
                return movieMapper.selectRecMovieById(candidatedIds.subList(0,size));
            }
            //排序层
            Embedding movieEmbbeding = new Embedding();
            movieEmbbeding.setEmbedVector(Embedding.parseStrToEmbArray(movieStrEmb));
            HashMap<Integer,RecMovieDto> candidatesMovie = filterSimilarMovieIds(movieId,movie);
            List<RecMovieDto> candidateMovieList = new ArrayList<>(candidatesMovie.values());
            List<RecMovieDto> newCandidateMovieList = new ArrayList<>();
            //将候选集的每个电影赋值一个Embedding属性
            for (RecMovieDto recMovieDto : candidateMovieList) {
                String otherMovieStrEmb = redisRepository.selectMovieDeepWalkEmb(recMovieDto.getMovieId());
                // 因为Emb的采样可能没有覆盖到所有的电影，这些都是比较偏远的，没有人看过的
                if (otherMovieStrEmb == null){
                    continue;
                }
                Embedding otherMovieEmb = new Embedding();
                otherMovieEmb.setEmbedVector(Embedding.parseStrToEmbArray(otherMovieStrEmb));
                recMovieDto.setEmb(otherMovieEmb);
                newCandidateMovieList.add(recMovieDto);
            }
            HashMap<RecMovieDto,Double> lastRecMovies = new HashMap<>();
            for (RecMovieDto recMovieDto : newCandidateMovieList) {
                //分别计算目标电影Embedding与候选电影Embedding的相似度
                Double similarity = movieEmbbeding.calculateSimilarity(recMovieDto.getEmb());
                lastRecMovies.put(recMovieDto,similarity);
            }
            List<RecMovieDto> results = new ArrayList<>();
//            HashMap<RecMovieDto,Double> limitRecMovies = new HashMap<>();
            Set<Map.Entry<RecMovieDto,Double>> recMovieSets = lastRecMovies.entrySet();
            List<Map.Entry<RecMovieDto,Double>> recMovieLists = new ArrayList<>(recMovieSets);
            Collections.sort(recMovieLists, new Comparator<Map.Entry<RecMovieDto,Double>>() {
                @Override
                public int compare(Map.Entry<RecMovieDto,Double> o1, Map.Entry<RecMovieDto,Double> o2) {
                    return o2.getValue().compareTo(o1.getValue());
                }
            });
//            lastRecMovies.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).forEach(m -> limitRecMovies.put(m.getKey(),m.getValue()));
//            lastRecMovies.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).forEach(m -> results.add(m.getKey()));
            if (recMovieLists.size() > size){
                redisRepository.savaMovieSimilars(recMovieLists.subList(0,size),movieId);
                return recMovieLists.subList(0,size).stream().map(Map.Entry::getKey).collect(Collectors.toList());
//                return results.subList(0,size);
            }
            //@TODO 这里得到的结果可能不足size个数
            redisRepository.savaMovieSimilars(recMovieLists,movieId);
            return recMovieLists.stream().map(Map.Entry::getKey).collect(Collectors.toList());
        }else{//缓存中有相似电影ID
            return movieMapper.selectRecMovieByIdInRedis(similarMovies);
        }
    }

    @Override
    public MovieDetail getMovieById(Integer movieId) {
        Integer userId = (Integer) ThreadLocalMap.get("THREAD_LOCAL_KEY_LOGIN_USER");
        if (userId != null){
            redisRepository.saveUserHistory(userId,movieId);
        }
        return movieMapper.selectMovieDetailById(movieId);
    }

    @Override
    public String collectMovie(Integer movieId) {
//        System.out.println("ssssss"+movieId);
        Integer userId = (Integer) ThreadLocalMap.get("THREAD_LOCAL_KEY_LOGIN_USER");
        int isCollect = movieMapper.selectIsMovieCollect(userId,movieId);
        if(isCollect == 0){//尚未收藏
            Date curDate = new Date();
//            SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
            movieMapper.insertMovieCollect(userId,movieId,curDate);
            return "收藏成功";
        }else{
            movieMapper.deleteMovieCollect(userId,movieId);
            return "取消收藏";
        }
    }

    @Override
    public Integer icCollectMovie(Integer movieId) {
        Integer userId = (Integer) ThreadLocalMap.get("THREAD_LOCAL_KEY_LOGIN_USER");
        int isCollect = movieMapper.selectIsMovieCollect(userId,movieId);
        if(isCollect == 0){//尚未收藏
            return 0;
        }else{
            return 1;
        }
    }

    @Override
    public Integer ratingMovie(MovieRatingForm movieRatingForm) {
        Integer userId = (Integer) ThreadLocalMap.get("THREAD_LOCAL_KEY_LOGIN_USER");
        Date curDate = new Date();
        Double rating = movieMapper.selectMovieRating(userId,movieRatingForm.getMovieId());
        int updateRatingTag = 0;//用户对该电影的评分是否存在，是第一次评分还是修改评分
        if (rating==null){//用户第一次对该电影评分
            //@TODO 需要加入对电影ID不存在的异常处理
            movieMapper.insertMovieRating(userId,movieRatingForm.getMovieId(),movieRatingForm.getMovieRating(),curDate);
        }else{
            movieMapper.updateMovieRating(userId,movieRatingForm.getMovieId(),movieRatingForm.getMovieRating(),curDate);
            updateRatingTag = 1;
        }
        threadService.updateUserStat(userId,updateRatingTag,movieRatingForm);
        threadService.updateMovieStat(movieRatingForm,updateRatingTag);
        return 1;
    }
    @Override
    public double selectMovieRating(Integer movieId) {
        Integer userId = (Integer) ThreadLocalMap.get("THREAD_LOCAL_KEY_LOGIN_USER");
        Double rating = movieMapper.selectMovieRating(userId,movieId);
        if (rating==null){
            return 0;
        }
        return rating;
    }
}
